//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi.display;

import java.io.IOException;
import java.util.Hashtable;

import ibook.v10.idvi.IDVI;
import ibook.v10.idvi.dvi.DVIFormatException;
import ibook.v10.idvi.dvi.DVITokenizer;
import ibook.v10.idvi.dvi.DVIDocument;
import ibook.v10.parameter.HashtableParameterStub;
import ibook.v10.parameter.ParameterStub;
import ibook.v10.parameter.ParameterUnionStub;

public class PageParser {
    private DVITokenizer    tokenizer;
    private DVIDocument     document;
    private int             pageIndex;
    private int             toggleIndex = 0;

    public PageParser( DVITokenizer tokenizer, DVIDocument document, int pageIndex ) {
        this.tokenizer = tokenizer;
        this.document = document;
        this.pageIndex = pageIndex;
    }

    public void close( ) throws IOException {
        tokenizer.close( );
    }

    //  startPage gets things started for a new page in a trivial way.
    //  This is used by the loader thread in DVIStreamDocument to obtain
    //  a BlockRoot object to be made available, before really loading
    //  the page.  This is what allows progressive display during loading.
    public BlockRoot startPage( ) {
        BlockRoot root = new BlockRoot( );
        ContainerBlock block = new ContainerBlock( );
        root.setBlock( block );

        return root;
    }

    //  finishPage does the real work of loading a page.
    public void finishPage( BlockRoot root ) throws IOException, DVIFormatException {
        if( tokenizer.token != DVITokenizer.kTokenBeginPage )
            throw new DVIFormatException( "missing beginning of page command" );
        
        tokenizer.advance( );

        parseMultilineContainer(( ContainerBlock ) root.getBlock( ), root );

        if( tokenizer.token != DVITokenizer.kTokenEndPage )
            throw new DVIFormatException( "missing end of page command" );
        
        tokenizer.advance( );
    }

    private void parseMultilineContainer( ContainerBlock containerBlock, BlockRoot root )
            throws IOException, DVIFormatException {
        
        boolean done = false;

        while( ! done )
            switch( tokenizer.token ) {
                case DVITokenizer.kTokenCharacter:
                case DVITokenizer.kTokenRule:
                case DVITokenizer.kTokenPSFile:
                    parseOnelineContainerIntoContainer( containerBlock, root );
                    break;

                case DVITokenizer.kTokenBeginLinkAnchor:
                    parseLinkIntoContainer( containerBlock, root );
                    break;

                case DVITokenizer.kTokenBeginNameAnchor:
                    parseNameIntoContainer( containerBlock, root );
                    break;

                case DVITokenizer.kTokenBeginColor:
                    parseColorIntoContainer( containerBlock, root );
                    break;
                
                case DVITokenizer.kTokenBeginApplet:
                    parseAppletIntoContainer( containerBlock, root );
                    break;
                
                default:
                    done = true;
                    break;
            }

        containerBlock.flushChildren( );
        containerBlock.doneAddingChildren( );
    }

    private void parseOnelineContainer( ContainerBlock containerBlock, BlockRoot root )
            throws IOException, DVIFormatException {
        
        boolean done = false;
        boolean initialized = false;
        int     lastX = 0;
        int     lastY = 0;
        
        while( ! done )
            switch( tokenizer.token ) {
                case DVITokenizer.kTokenCharacter:
                    if( ! initialized ) {
                        lastX = tokenizer.characterUnscaledX;
                        lastY = tokenizer.characterUnscaledY;
                        initialized = true;
                    }

                    if( Math.abs( lastY - tokenizer.characterUnscaledY ) < 200 &&
                            lastX - tokenizer.characterUnscaledX < 300 ) {
                        parseCharacterIntoContainer( containerBlock );
                    } else {
                        done = true;
                    }
                    break;
                
                case DVITokenizer.kTokenRule:
                    parseRuleIntoContainer( containerBlock );
                    break;
                
                case DVITokenizer.kTokenPSFile:
                    parsePSFileIntoContainer( containerBlock );
                    break;

                default:
                    done = true;
            }

        containerBlock.flushChildren( );
        containerBlock.doneAddingChildren( );
    }

    private void parseOnelineContainerIntoContainer( ContainerBlock container, BlockRoot root )
            throws IOException, DVIFormatException {

        ContainerBlock child = new ContainerBlock( );
        container.addChild( child, 0, 0 );
        container.flushChildren( );
        parseOnelineContainer( child, root );
    }

    private void parseCharacterIntoContainer( ContainerBlock container ) 
            throws IOException, DVIFormatException {

        CharacterBlock characterBlock =
            CharacterBlock.getCharacterBlock( tokenizer.character );

        container.addChild( characterBlock, tokenizer.characterUnscaledX, tokenizer.characterUnscaledY );

        tokenizer.advance( );
    }

    private void parseRuleIntoContainer( ContainerBlock container )
            throws IOException, DVIFormatException {

        RuleBlock ruleBlock =
            RuleBlock.getRuleBlock( tokenizer.ruleUnscaledWidth, tokenizer.ruleUnscaledHeight );

        container.addChild( ruleBlock, tokenizer.ruleUnscaledX, tokenizer.ruleUnscaledY );
    
        tokenizer.advance( );
    }

    private void parsePSFileIntoContainer( ContainerBlock container )
            throws IOException, DVIFormatException {

        ImageBlock imageBlock =
            ImageBlock.getImageBlock(
                tokenizer.psFilePrefix,
                tokenizer.psFileUnscaledWidth,
                tokenizer.psFileUnscaledHeight );
            
        container.addChild( imageBlock, tokenizer.psFileUnscaledX, tokenizer.psFileUnscaledY );

        tokenizer.advance( );
    }

    private void parseLinkIntoContainer( ContainerBlock container, BlockRoot root )
            throws IOException, DVIFormatException {
        
        String href = tokenizer.href;
        String target = tokenizer.target;
        tokenizer.advance( );

        if(( IDVI.kIDVIURLPrefix + IDVI.kToggleURLPrefix ).equals( href ))
            parseToggleActionIntoContainer( href + '#' + toggleIndex, target, container, root );
        else
            parseLinkActionIntoContainer( href, target, container, root );

        if( tokenizer.token != DVITokenizer.kTokenEndAnchor )
            throw new DVIFormatException( "missing expected html:</a> special" );
        
        tokenizer.advance( );
    }       

    private void parseNameIntoContainer( ContainerBlock container, BlockRoot root )
            throws IOException, DVIFormatException {

        String name = tokenizer.name;
        tokenizer.advance( );

        ContainerBlock child = new ContainerBlock( );

        String prefix = IDVI.kIDVIURLPrefix + IDVI.kToggleURLPrefix;
        if( prefix.regionMatches( true, 0, name, 0, prefix.length( ))) {
            ToggleBlock toggle = new ToggleBlock( false );
            toggle.setChild( child );
            container.addChild( toggle, 0, 0 );

            root.putNamedBlock( prefix + '#' + toggleIndex, toggle );
            if( name.length( ) != prefix.length( ))
                root.putNamedBlock( name, toggle );
            
            toggleIndex ++;
        } else {
            container.addChild( child, 0, 0 );
            root.putNamedBlock( name, child );
        }

        container.flushChildren( );
        parseMultilineContainer( child, root );

        if( tokenizer.token != DVITokenizer.kTokenEndAnchor )
            throw new DVIFormatException( "missing expected html:</a> special" );
        
        tokenizer.advance( );
    }       

    private void parseColorIntoContainer( ContainerBlock container, BlockRoot root )
            throws IOException, DVIFormatException {

        ColorBlock color = new ColorBlock( tokenizer.color );
        ContainerBlock child = new ContainerBlock( );
        tokenizer.advance( );

        color.setChild( child );
        container.addChild( color, 0, 0 );
        container.flushChildren( );

        parseMultilineContainer( child, root );

        if( tokenizer.token != DVITokenizer.kTokenEndColor )
            throw new DVIFormatException( "missing expected color pop special" );
        
        tokenizer.advance( );
    }

    private void parseAppletIntoContainer( ContainerBlock container, BlockRoot root )
            throws IOException, DVIFormatException {
        
        ParameterStub appletTagParameter = tokenizer.appletParameter;
        String code = tokenizer.appletCode;

        tokenizer.advance( );

        Hashtable paramTagHashtable = new Hashtable( 5 );
        DVIRectangle hiddenBounds = new DVIRectangle( );

        int appletStack = 1;
        while( appletStack > 0 )
            switch( tokenizer.token ) {
                case DVITokenizer.kTokenParam:
                    if( appletStack == 1 )
                        paramTagHashtable.put( tokenizer.paramName.toLowerCase( ), tokenizer.paramValue );

                    tokenizer.advance( );
                    break;

                case DVITokenizer.kTokenBeginApplet:
                    ++ appletStack;
                    tokenizer.advance( );
                    break;

                case DVITokenizer.kTokenEndApplet:
                    -- appletStack;
                    tokenizer.advance( );
                    break;

                case DVITokenizer.kTokenCharacter:
                    ignoreCharacterSaveBounds( hiddenBounds );
                    tokenizer.advance( );
                    break;
                
                case DVITokenizer.kTokenRule:
                    ignoreRuleSaveBounds( hiddenBounds );
                    tokenizer.advance( );
                    break;

                default:
                    tokenizer.advance( );
                    break;
            }
        
        ParameterStub unionParameter;

        if( paramTagHashtable.isEmpty( )) {
            unionParameter = appletTagParameter;
        } else {
            System.out.println( "param tags: " + paramTagHashtable );

            ParameterStub paramTagParameter = new HashtableParameterStub( paramTagHashtable );
            ParameterUnionStub tempUnionParameter = new ParameterUnionStub( );
            tempUnionParameter.add( appletTagParameter );
            tempUnionParameter.add( paramTagParameter );
            unionParameter = tempUnionParameter;
        }

        if( hiddenBounds.unset ) {
            System.out.println( "Applet \"" + code + "\" could not be positioned on the page.  It contains no text." );
        } else {
            int unscaledWidth = hiddenBounds.right - hiddenBounds.left;
            int unscaledHeight = hiddenBounds.bottom - hiddenBounds.top;
            int unscaledX = ( hiddenBounds.left + hiddenBounds.right ) / 2;
            int unscaledY = ( hiddenBounds.top + hiddenBounds.bottom ) / 2;

            AppletBlock appletBlock = new AppletBlock( code, unscaledWidth, unscaledHeight, unionParameter );
            container.addChild( appletBlock, unscaledX, unscaledY );
            container.flushChildren( );
        }
    }

    private void parseLinkActionIntoContainer( String href, String target, ContainerBlock container, BlockRoot root )
            throws IOException, DVIFormatException {

        ActionBlockGroup group = new ActionBlockGroup( href, target );

        while( tokenizer.token != DVITokenizer.kTokenEndAnchor ) {
            ActionBlock action = new LinkActionBlock( group );
            ContainerBlock child = new ContainerBlock( );

            action.setChild( child );
            container.addChild( action, 0, 0 );
            container.flushChildren( );

            parseOnelineContainer( child, root );
            action.childDoneLoading( );
        }

        group.doneAddingActionBlocks( );
    }

    private void parseToggleActionIntoContainer( String href, String target, ContainerBlock container, BlockRoot root )
            throws IOException, DVIFormatException {

        ActionBlockGroup group = new ActionBlockGroup( href, target );
        ActionBlock action = new ToggleActionBlock( group );
        ContainerBlock child = new ContainerBlock( );

        action.setChild( child );
        container.addChild( action, 0, 0 );
        container.flushChildren( );

        parseMultilineContainer( child, root );
        action.childDoneLoading( );

        group.doneAddingActionBlocks( );
    }       

    private void ignoreCharacterSaveBounds( DVIRectangle bounds ) {
        int top = tokenizer.characterUnscaledY - tokenizer.character.getYOffset( );
        int left = tokenizer.characterUnscaledX - tokenizer.character.getXOffset( );

        bounds.union( top, left, top + tokenizer.character.getHeight( ), left + tokenizer.character.getWidth( ));
    }

    private void ignoreRuleSaveBounds( DVIRectangle bounds ) {
        bounds.union(
            tokenizer.ruleUnscaledY - tokenizer.ruleUnscaledHeight,
            tokenizer.ruleUnscaledX,
            tokenizer.ruleUnscaledY,
            tokenizer.ruleUnscaledX + tokenizer.ruleUnscaledWidth );
    }
}
